#pragma once

#include "Connection.hpp"
#include "Sock.hpp"
#include "Epoll.hpp"
#include "Common.hpp"

#include <unordered_map>

using callback_func = std::function<std::string(std::shared_ptr<Connection>)>;

class Rserver
{
    static const int array_num_max = 1024;

public:
    Rserver(uint16_t port, callback_func Onmessage)
        : _sock_ptr(new Sock(port)),
          _epoll_ptr(new Epoll),
          _Onmessage(Onmessage)
    {
    }

    void Recv(std::shared_ptr<Connection> con_ptr)
    {
        // 1. 将缓冲区中的数据全部读取到Connection中
        // 2. 调用外界函数判断是否含有一个完成的报文
        // 3. 先客户端返回结果

        char inbuffer[1024];
        while (1)
        {
            int n = read(con_ptr->Get_fd(), inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                // 有数据
                inbuffer[n] = 0;
                con_ptr->Add_In(inbuffer);
            }
            else if (n == 0)
            {
                // 对方关闭了文件 , 断开连接了

                // 1. 将文件描述符从epoll模型中移除
                // 2. 将文件描述符从哈希表中移除
                // 3. 将文件描述符关闭

                int fd = con_ptr->Get_fd();
                _epoll_ptr->Del_fd(fd);
                _connections.erase(fd);

                close(fd);
                return;
            }
            else
            {
                // 此次有两种情况: 1. 数据读取完了   2. 读取出错了
                if (errno == EAGAIN) // 读取完了
                {
                    break;
                }
                else // 出错了
                {
                    // 此处调用文件对应的异常处理
                    con_ptr->_Exception(con_ptr);
                    return;
                }
            }
        }

        std::string ret = _Onmessage(con_ptr);
        con_ptr->Add_Out(ret);
        // con_ptr->_Sender(con_ptr);
    }

    void Sender(std::shared_ptr<Connection> con_ptr)
    {
        // 进行数据的发送
        // 直接进行发送

        std::string& outbuffer = con_ptr->Get_Outbuffer();
        int fd = con_ptr->Get_fd();
        // 循环式的进行发送
        while (1)
        {
            int n = write(fd, outbuffer.c_str(), outbuffer.size());
            if (n > 0)
            {
                // 1. 将已经发送的数据从字符串中移除
                outbuffer.erase(0, n);
                if (outbuffer.empty())
                    break; // 已经写完了
            }
            else if (n == 0)
            {
                break;
            }
            else
            {
                if (errno == EAGAIN) // 已经写完了
                    break;
                else // 出错了
                {
                    // 此处调用文件对应的异常处理
                    con_ptr->_Exception(con_ptr);
                    return;
                }
            }
        }

        // 判断发送缓冲区中是否还有数据
        if (!outbuffer.empty())
        {
            // 发送缓冲区满了
            _epoll_ptr->Mod_fd(fd, EPOLLIN | EPOLLOUT | EPOLLET);
        }
        else
        {
            // 缓冲区没满 , 不需要对写事件进行检测
            _epoll_ptr->Mod_fd(fd, EPOLLIN | EPOLLET);
        }
    }

    void Exception(std::shared_ptr<Connection> con_ptr)
    {
        int fd = con_ptr->Get_fd();
        _epoll_ptr->Del_fd(fd);
        _connections.erase(fd);
        close(fd);
    }

    void Accept(std::shared_ptr<Connection> con_ptr)
    {
        // 1. 获取文件描述符
        while (1)
        {
            int newfd = _sock_ptr->Accept();
            if (newfd >= 0)
            {
                // 有新连接
                // 2. 将文件描述符设置为非阻塞
                // 3. 将文件加入到epoll模型中
                // 4. 将文件描述符加入到哈希表中
                if (SetNoBlock(newfd) < 0)
                {
                    Log(Warning) << "set no block fail";
                    continue;
                }
                _epoll_ptr->Add_fd(newfd, EPOLLIN | EPOLLET);
                std::shared_ptr<Connection> con_ptr(new Connection(newfd,
                                                                   std::bind(&Rserver::Recv, this, std::placeholders::_1),
                                                                   std::bind(&Rserver::Sender, this, std::placeholders::_1),
                                                                   std::bind(&Rserver::Exception, this, std::placeholders::_1)));

                _connections.emplace(newfd, con_ptr);
            }
            else
            {
                if (errno == EAGAIN)
                    break;
                else
                {
                    // 出错了
                    Log(Warning) << "accept fail";
                }
            }
        }
    }

    void Init()
    {
        // 1. 创建套接字
        // 2. 进行绑定
        // 3. 设置监听模式
        // 4. 将网络套接字加入到epoll模型中,并创建Connection加入到_connections中进行管理

        _sock_ptr->Socket();
        _sock_ptr->Bind();
        _sock_ptr->Listen();

        int listensock = _sock_ptr->Get_fd();
        SetNoBlock(listensock);
        _epoll_ptr->Add_fd(listensock , EPOLLIN | EPOLLET);
        std::shared_ptr<Connection> conptr(new Connection(listensock,
                                                          std::bind(&Rserver::Accept, this, std::placeholders::_1),
                                                          nullptr, nullptr));

        _connections.emplace(listensock, conptr);


        // 将IP和端口号设置为可复用的
        int opt = 1;
        setsockopt(listensock , SOL_SOCKET , SO_REUSEADDR | SO_REUSEPORT , &opt , sizeof(opt));
    }

    void Dispatcher(int n)
    {
        for (int i = 0; i < n; i++)
        {
            int fd = _epl_array[i].data.fd;
            short events = _epl_array[i].events;
            auto &con_ptr = _connections[fd];

            // 将异常处理, 转化为读写处理
            if (events & EPOLLERR)
            {
                events |= (EPOLLIN | EPOLLOUT);
            }

            if (_connections.count(fd) && con_ptr->_Recv)
            {
                con_ptr->_Recv(con_ptr);
            }
            if (_connections.count(fd) && con_ptr->_Sender)
            {
                con_ptr->_Sender(con_ptr);
            }
        }
    }

    void Run()
    {
        while (1)
        {
            int n = _epoll_ptr->Wait(_epl_array, array_num_max, -1);
            if (n > 0)
            {
                Dispatcher(n);
            }
            else if (n == 0)
            {
                Log(Info) << "no message";
            }
            else
            {
                Log(Warning) << "epoll wait fail";
            }
        }
    }

private:
    std::shared_ptr<Sock> _sock_ptr;
    std::shared_ptr<Epoll> _epoll_ptr;
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;
    struct epoll_event _epl_array[array_num_max];

    callback_func _Onmessage;
};